#include "jvm.h"
#include <dlfcn.h>
#include <string.h>
#include <vector>
#include <pwd.h>

Jvm::Jvm() : common(Common::getInstance()), properties(Properties::getInstance()) {}

Error Jvm::start() {
    if (!load()) {
        return ERR_LOAD_JVM_DLL;
    }
    if (!createJavaVM()) {
        return ERR_CREATE_JVM;
    }
    if (!runMainClass()) {
        return ERR_MAIN_CLASS_NOT_FOUND;
    }
    return ERR_OK;
}

bool Jvm::load() {
    if (ptrCreateJavaVM) {
        return true;
    }

    // expand java home
    std::list<String> javaHome = properties.getJavaHome();
    if (javaHome.empty()) {
        throw std::runtime_error("exe.javaHome is not set!");
    }

    // find jvm.dll and load
    String jvmLibSuffix[] = {
        "/lib/client/libjvm.so",
        "/lib/server/libjvm.so",
        "/jre/lib/client/libjvm.so",
        "/jre/lib/server/libjvm.so",
    };
    for (String home : javaHome) {
        for (String path : jvmLibSuffix) {
            String fullPath = home + path;
            if (common.isFileExists(fullPath)) {
                if (debugEnable) {
                    common.debug("load: " + fullPath);
                }
                void *libHandle = dlopen(fullPath.c_str(), RTLD_LOCAL | RTLD_LAZY);
                if (libHandle) {
                    ptrCreateJavaVM = (PtrCreateJavaVM)dlsym(libHandle, "JNI_CreateJavaVM");
                    if (ptrCreateJavaVM) {
                        // dlclose(libHandle);
                        return true;
                    }
                }
            }
        }
    }

    return ptrCreateJavaVM != nullptr;
}

bool Jvm::createJavaVM() {
    // prepare vm options
    std::list<String> vmOptions = properties.getVmOptions();

    // add classpath option
    String fullClassPath = properties.getClassPath();
    vmOptions.push_back("-Djava.class.path=" + fullClassPath);

    // add user.dir
    vmOptions.push_back("-Duser.dir=" + common.getExeDir());

    std::vector<JavaVMOption> opts;
    for (String opt : vmOptions) {
        JavaVMOption item;
        item.optionString = strdup(opt.c_str());
        item.extraInfo = 0;
        opts.push_back(item);
    }

    JavaVMInitArgs initArgs;
    initArgs.version = JNI_VERSION_1_6;
    initArgs.options = opts.data();
    initArgs.nOptions = static_cast<jint>(opts.size());
    initArgs.ignoreUnrecognized = JNI_FALSE;
    return ptrCreateJavaVM(&jvm, &env, &initArgs) == JNI_OK;
}

bool Jvm::runMainClass() {
    String mainClass = properties.getMainClass();
    if (mainClass.empty()) {
        return false;
    }

    jclass jclassMain = env->FindClass(mainClass.c_str());
    if (!jclassMain) {
        return false;
    }

    jmethodID mainMethod = env->GetStaticMethodID(jclassMain, "main", "([Ljava/lang/String;)V");
    if (!mainMethod) {
        return false;
    }

    std::list<String> mainArgs = properties.getMainArgs();
    size_t size = mainArgs.size();
    jobjectArray args = (jobjectArray)env->NewObjectArray(static_cast<jsize>(size), env->FindClass("java/lang/String"),
                                                          env->NewStringUTF(""));
    int i = 0;
    for (String arg : mainArgs) {
        env->SetObjectArrayElement(args, static_cast<jsize>(i++), env->NewStringUTF(arg.c_str()));
    }
    env->CallStaticVoidMethod(jclassMain, mainMethod, args);
    jthrowable exception = env->ExceptionOccurred();
    if (exception) {
        // throw std::runtime_error("Run main method has exception!");
        common.showMessageBox(true, "运行main方法时发生异常！");
    }
    jvm->DestroyJavaVM();
    return true;
}
